package org.scs.carleton.ca.pgdt.core.ui.editors;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.editors.text.TextEditor;
import org.scs.carleton.ca.pgdt.core.ParseException;
import org.scs.carleton.ca.pgdt.core.PrologCore;
import org.scs.carleton.ca.pgdt.core.internal.parser.TokenMgrError;
import org.scs.carleton.ca.pgdt.core.parser.nodes.IASTProgram;
import org.scs.carleton.ca.pgdt.core.parser.nodes.IASTTranslationUnit;
import org.scs.carleton.ca.pgdt.core.ui.internal.highlighting.ColorManager;
import org.scs.carleton.ca.pgdt.core.ui.internal.highlighting.IPrologReconcilingListener;
import org.scs.carleton.ca.pgdt.core.ui.internal.highlighting.PrologDocumentProvider;
import org.scs.carleton.ca.pgdt.core.ui.internal.highlighting.PrologHighlightingManager;
import org.scs.carleton.ca.pgdt.core.ui.internal.highlighting.PrologHighlightingReconciler;
import org.scs.carleton.ca.pgdt.core.ui.internal.highlighting.PrologSourceViewerConfiguration;

/**
 * The core PrologEditor with syntax highlighting and all of that voodoo magic.
 * 
 * @author tessierj
 *
 */
public class PrologEditor extends TextEditor implements IPrologReconcilingListener, IResourceChangeListener {
	private ColorManager colorManager;
	private PrologHighlightingManager highlightingManager;
	private Set<IPrologReconcilingListener> reconcilingListeners;

	public PrologEditor() {
		super();
		reconcilingListeners = new HashSet<IPrologReconcilingListener>(10);
		colorManager = new ColorManager();
		setSourceViewerConfiguration(createPrologSourceViewerConfiguration());
		setDocumentProvider(new PrologDocumentProvider());
	}

	@Override
	public void createPartControl(Composite parent) {
		super.createPartControl(parent);
		initializeHighlighting();
	}

	private synchronized void initializeHighlighting() {
		if (highlightingManager == null) {
			ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
			this.highlightingManager = new PrologHighlightingManager();
			this.highlightingManager.install(this, getSourceViewer(), colorManager);
		}
	}

	public void dispose() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
		colorManager.dispose();
		super.dispose();
	}

	public PrologSourceViewerConfiguration createPrologSourceViewerConfiguration() {
		return new PrologSourceViewerConfiguration(colorManager);
	}

	public IASTProgram getPrologProgram() {
		IASTProgram program = null;
		IEditorInput input = getEditorInput();
		if (input instanceof IFileEditorInput) {
			IFile file = ((IFileEditorInput) input).getFile();
			try {
				program = PrologCore.getDefault().getASTProvider().getProgram(file);
				//Remove the error marker if there is one, we successfully parsed
				EditorMarkerPool.getInstance().removeErrorMarker(file);
			}
			catch (ParseException e) {
				EditorMarkerPool.getInstance().createErrorMarker(file, e);
			}
			catch (TokenMgrError e) {
				EditorMarkerPool.getInstance().createErrorMarker(file, e);
			}
		}

		return program;
	}

	@Override
	protected void doSetInput(IEditorInput input) throws CoreException {
		super.doSetInput(input);

		highlight();
	}

	@Override
	public void reconciled(IASTTranslationUnit compilationUnit, boolean forced, IProgressMonitor monitor) {
		for (IPrologReconcilingListener listener : reconcilingListeners) {
			listener.reconciled(compilationUnit, forced, monitor);
		}
	}

	@Override
	public void preReconciliation() {
		for (IPrologReconcilingListener listener : reconcilingListeners) {
			listener.preReconciliation();
		}
	}

	public final void addReconcileListener(IPrologReconcilingListener listener) {
		reconcilingListeners.add(listener);
	}

	@Override
	public void resourceChanged(IResourceChangeEvent event) {
		IResourceDelta delta = event.getDelta();
		if (event.getType() == IResourceChangeEvent.POST_CHANGE && delta != null) {
			AvoidMarkerVisitor vis = new AvoidMarkerVisitor();
			
			try {
				delta.accept(vis);
				if (vis.hasChanges()) {
					highlight();
				}
			}
			catch (CoreException e) {
				//Don't try to highlight then
			}
		}
	}

	private void highlight() {
		Job job = new Job("Syntax highlighting") { //$NON-NLS-1$
			/*
			 * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
			 * 
			 * @since 3.0
			 */
			protected IStatus run(IProgressMonitor monitor) {
				if (highlightingManager == null)
					initializeHighlighting();
				PrologHighlightingReconciler reconciler = highlightingManager.getHighlightingReconciler();
				if (reconciler != null) {
					IASTProgram program = getPrologProgram();
					//Only update if we can
					if (program != null) {
						reconciler.reconciled(getPrologProgram().getTranslationUnit(), false, monitor);
					}
				}
				return Status.OK_STATUS;
			}
		};
		job.setPriority(Job.DECORATE);
		job.setSystem(true);
		job.schedule();
	}
	
	private class AvoidMarkerVisitor implements IResourceDeltaVisitor {
		private boolean contentChanges = false;
		
		@Override
		public boolean visit(IResourceDelta delta) throws CoreException {
			contentChanges = ((delta.getFlags() & IResourceDelta.CONTENT) != 0) || contentChanges;
			return !contentChanges; //We don't need to keep looking if we have content changes
		}
		
		private boolean hasChanges() {
			return contentChanges;
		}
	}
}
